/**
 * Copyright (c) 2011 eelpo.com
 * All rights reserved.
 */
package com.eelpo.common.encoder;

import java.io.*;

/**
 * BASE64算法解码类
 * 
 * @author <a href="mailto:Brian@eelpo.com">汪洪州</a>
 * @since 1.0
 */

public class BASE64DecoderStream extends FilterInputStream {

	/**
	 * 缓存解码的字节码
	 */
	private byte[] buffer;

	/**
	 * 解码块,解码中的字节数组
	 */
	private byte[] decode_buffer = new byte[4];

	/**
	 * 缓存字节码的大小
	 */
	private int bufsize = 0;

	/**
	 * 缓存索引
	 */
	private int index = 0;

	/**
	 * 转换字节数组
	 */
	private final static byte PEM_CONVERT_ARRAY[] = new byte[256];

	/**
	 * BASE64（RFC1521）中的64个字符
	 * 
	 */
	private final static char PEM_ARRAY[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0
			'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1
			'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 2
			'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 3
			'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 4
			'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 5
			'w', 'x', 'y', 'z', '0', '1', '2', '3', // 6
			'4', '5', '6', '7', '8', '9', '+', '/' // 7
	};

	static {
		for (int i = 0; i < 255; i++)
			PEM_CONVERT_ARRAY[i] = -1;
		for (int i = 0; i < PEM_ARRAY.length; i++)
			PEM_CONVERT_ARRAY[PEM_ARRAY[i]] = (byte) i;
	}

	/**
	 * 根据指定的输入流构件BASE64解码类
	 * 
	 * @param in
	 *            输入流
	 */
	public BASE64DecoderStream(InputStream in) {
		super(in);
		buffer = new byte[3];
	}

	public static byte[] decode(String in) {
		return decodeBuffer(in.getBytes());
	}

	/**
	 * 解码
	 * 
	 * @param inbuf
	 * @return
	 */
	public static byte[] decodeBuffer(byte[] inbuf) {

		// 计算解码时候输出的字节长度
		int size = (inbuf.length / 4) * 3;

		// 判断字节是否为空数组
		if (size == 0)
			return inbuf;

		// 检查结果的等于符号，判断解码之后的字节长度
		if (inbuf[inbuf.length - 1] == '=') {
			size--;
			if (inbuf[inbuf.length - 2] == '=')
				size--;
		}

		// 初始化解码之后的字节数组
		byte[] outbuf = new byte[size];

		// 记录编码字节处理的个数和解码字节处理的个数
		int inpos = 0, outpos = 0;

		// 记录编码字节数组的长度
		size = inbuf.length;

		while (size > 0) {

			byte a, b;
			a = PEM_CONVERT_ARRAY[inbuf[inpos++] & 0xff];
			b = PEM_CONVERT_ARRAY[inbuf[inpos++] & 0xff];

			// 解码的第一个字节
			outbuf[outpos++] = (byte) (((a << 2) & 0xfc) | ((b >>> 4) & 3));

			// 判断是否是等于符号结尾
			if (inbuf[inpos] == '=')
				return outbuf;

			a = b;
			b = PEM_CONVERT_ARRAY[inbuf[inpos++] & 0xff];

			// 解码的第二个字节
			outbuf[outpos++] = (byte) (((a << 4) & 0xf0) | ((b >>> 2) & 0xf));

			// 判断是否是等于符号结尾
			if (inbuf[inpos] == '=')
				return outbuf;

			a = b;
			b = PEM_CONVERT_ARRAY[inbuf[inpos++] & 0xff];

			// 解码的第三个字节
			outbuf[outpos++] = (byte) (((a << 6) & 0xc0) | (b & 0x3f));

			size -= 4;
		}

		return outbuf;
	}

	private void decode() throws IOException {
		bufsize = 0;

		int got = 0;
		while (got < 4) {
			int i = in.read();

			// 判断是否到了流的末尾
			if (i == -1) {

				// 如果got等于0，表示为一个空的输入流，抛出异常
				if (got == 0)
					return;
				throw new IOException("错误的字节数");
			}
			if (i >= 0 && i < 256 && i == '=' || PEM_CONVERT_ARRAY[i] != -1)
				decode_buffer[got++] = (byte) i;
		}

		byte a, b;
		a = PEM_CONVERT_ARRAY[decode_buffer[0] & 0xff];
		b = PEM_CONVERT_ARRAY[decode_buffer[1] & 0xff];

		// 解码的第一个字节
		buffer[bufsize++] = (byte) (((a << 2) & 0xfc) | ((b >>> 4) & 3));

		// 判断是否是等于符号结尾
		if (decode_buffer[2] == '=')
			return;
		a = b;
		b = PEM_CONVERT_ARRAY[decode_buffer[2] & 0xff];

		// 解码的第二个字节
		buffer[bufsize++] = (byte) (((a << 4) & 0xf0) | ((b >>> 2) & 0xf));

		// 判断是否是等于符号结尾
		if (decode_buffer[3] == '=')
			return;
		a = b;
		b = PEM_CONVERT_ARRAY[decode_buffer[3] & 0xff];

		// 解码的第三个字节
		buffer[bufsize++] = (byte) (((a << 6) & 0xc0) | (b & 0x3f));
	}

	/**
	 * 
	 * @return 返回解码之后的int数字
	 * @throws IOException
	 *             抛出IO异常
	 */
	public int read() throws IOException {
		if (index >= bufsize) {
			decode();

			// 已经到末尾
			if (bufsize == 0)
				return -1;

			// 重置索引
			index = 0;
		}
		return buffer[index++] & 0xff;
	}

	/**
	 * 从输入流中读取解码的字节
	 * 
	 * @param buf
	 *            从输入流中读去字节到该字节数组中
	 * @param off
	 *            开始位置的偏移量
	 * @param len
	 *            一次最大读取的字节数
	 * @return 返回读取到的字接数， -1表示到了结尾处
	 * @throws IOException
	 *             抛出IO异常
	 */
	public int read(byte[] buf, int off, int len) throws IOException {
		int i, c;
		for (i = 0; i < len; i++) {
			if ((c = read()) == -1) {
				if (i == 0)
					i = -1;
				break;
			}
			buf[off + i] = (byte) c;
		}

		return i;
	}

	/**
	 * 返回能出输入流中读去的字节数
	 */
	public int available() throws IOException {
		return ((in.available() * 3) / 4 + (bufsize - index));
	}
}
